package org.xx.armory.commons;

import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import static org.xx.armory.commons.Validators.notNull;

/**
 * 使用复合键的{@code Map}
 *
 * <p>
 * 复合键Map的元素也可以是复合键Map, 所以复合键Map允许使用多级键名来引用数据, 比如:
 * </p>
 *
 * <pre>
 * Object oc = map.get("key1.key12");
 * Object o1 = map.get("key1");
 * Object o2 = ((CompKeyMap)o1).get("key2");
 * oc == o2; // true
 * </pre>
 *
 * @author Haart
 */
public class CompKeyMap
        implements Map<String, Object> {
    private static final char SEP = '.';

    private final Map<String, Object> data;

    /**
     * 构造复合键{@code Map}。
     */
    public CompKeyMap() {
        this.data = new TreeMap<>();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int size() {
        return this.data.size();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean isEmpty() {
        return this.data.isEmpty();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean containsKey(Object key) {
        return this.data.containsKey(key);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean containsValue(Object value) {
        return this.data.containsValue(value);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object get(Object key) {
        final String[] keys = getKeys(key);
        if (keys[1] == null) {
            return this.data.get(keys[0]);
        } else {
            final Object o1 = this.data.get(keys[0]);
            return ((CompKeyMap) o1).get(keys[1]);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object put(
            String key,
            Object value
    ) {
        final String[] keys = getKeys(key);
        if (keys[1] == null) {
            return this.data.put(keys[0], value);
        } else {
            Object o1 = this.data.computeIfAbsent(keys[0], k -> new CompKeyMap());

            return ((CompKeyMap) o1).put(keys[1], value);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object remove(Object key) {
        final String[] keys = getKeys(key);
        if (keys[1] == null) {
            return this.data.remove(keys[0]);
        } else {
            final Object o1 = this.data.get(keys[0]);
            return ((CompKeyMap) o1).remove(keys[1]);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void putAll(Map<? extends String, ?> m) {
        this.data.putAll(m);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void clear() {
        this.data.clear();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<String> keySet() {
        return this.data.keySet();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Collection<Object> values() {
        return this.data.values();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Set<java.util.Map.Entry<String, Object>> entrySet() {
        return this.data.entrySet();
    }

    /**
     * 根据复合键获取键数组。
     * <p>形如{@literal a.b.c}的复合键对应了数组{@literal [a, b, c]}</p>
     *
     * @param key
     *         复合键。
     * @return 键数组。
     * @throws IllegalArgumentException
     *         如果参数{@code key}是{@code null}。
     */
    private String[] getKeys(Object key) {
        notNull(key, "key");

        final String strKey = key instanceof CharSequence ? key.toString() : String.valueOf(key);
        final int si = strKey.indexOf(SEP);
        if (si == -1) {
            // 单一键
            return new String[]{strKey, null};
        } else {
            // 复合键
            return new String[]{strKey.substring(0, si), si == strKey.length() ? "" : strKey.substring(si + 1)};
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        return this.data.hashCode();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object obj) {
        return this == obj || obj != null && obj instanceof Map && this.data.equals(obj);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return this.data.toString();
    }
}
